using System.IO;
using System.Collections;
using System.Collections.Generic;


namespace QSharp.Scheme.Utility
{
    public class StreamProbe : IEnumerable<byte>
    {
    /* data types */

        public class Exception : System.Exception
        {
            public enum Error
            {
                kExceedingStreamLimit,
            };

            public readonly Error Err;
            public Exception(Error err)
            {
                Err = err;
            }
        }

    /*  */
        public Stream Target
        {
            set
            {
                myStream = value;
            }
        }

        public StreamProbe()
        {
            myPageSize = 1024;
            myCache = new EasyLRUCache(4);
            myStream = null;
        }

        public StreamProbe(Stream stream)
        {
            myPageSize = 1024;
            myCache = new EasyLRUCache(4);
            myStream = stream;
        }

        public StreamProbe(int nPageSize, int nPageNum)
        {
            myPageSize = nPageSize;
            myCache = new EasyLRUCache(nPageNum);
            myStream = null;
        }

        public StreamProbe(Stream stream, int nPageSize, int nPageNum)
        {
            myPageSize = nPageSize;
            myCache = new EasyLRUCache(nPageNum);
            myStream = stream;
        }


        public byte this[int nOffset]
        {
            get
            {
                LinkedListNode<EasyLRUCache.CachePage>  lruNode;
                int iByIndex;
                int iPage = nOffset / myPageSize;
                int iPosInPage = nOffset % myPageSize;

                if (myCache.Retrieve(out iByIndex, out lruNode, iPage))
                {
                    myCache.Hit(lruNode);
                }
                else
                {   // cache miss, loading from stream
                    iByIndex = myCache.Miss(iByIndex, iPage);
                    myStream.Seek(iPage * myPageSize, SeekOrigin.Begin);
                    EasyLRUCache.CachePageBase cpb = myCache[iByIndex];
                    if (cpb.Buf == null || cpb.Buf.Length != myPageSize)
                    {
                        cpb.Buf = new byte[myPageSize];
                    }
                    myCache[iByIndex].nLen = 
                        myStream.Read(cpb.Buf, 0, myPageSize);
                }

                if (iPosInPage >= myCache[iByIndex].nLen)
                {
                    throw new Exception(Exception.Error.kExceedingStreamLimit);
                }

                return myCache[iByIndex].Buf[iPosInPage];
            }
        }

        public IEnumerator<byte> GetEnumerator()
        {
            byte[] buf = new byte[myPageSize];

            for (int nOffset = 0; ; nOffset += myPageSize)
            {
                int nRead = Read(buf, nOffset, myPageSize);
                for (int i = 0; i < nRead; i++)
                {
                    byte b = buf[i];
                    yield return b;
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        protected void MemCopy(byte[] dest, int nDestOffset, byte[] src, int nSrcOffset, int nCopySize)
        {
            int nSrcEnd = nSrcOffset + nCopySize;
            for ( ; nSrcOffset < nSrcEnd; nDestOffset++, nSrcOffset++)
            {
                dest[nDestOffset] = src[nSrcOffset];
            }
        }

        public int Read(byte[] buf, int nOffset, int nSize)
        {
            LinkedListNode<EasyLRUCache.CachePage>  lruNode;
            int iPageBegin = nOffset / myPageSize;
            int iPageEnd = (nOffset + nSize - 1) / myPageSize;
            int nTotalRead = nSize;
            bool bExhausted = false;
            int nBufOffset = 0;

            for (int iPage = iPageBegin; !bExhausted && iPage <= iPageEnd; iPage++)
            {
                int iBeginInPage = nOffset % myPageSize;
                int nSizeInPage = myPageSize - iBeginInPage;
                int iByIndex;

                if (nSizeInPage > nSize)
                {
                    nSizeInPage = nSize;
                }

                if (myCache.Retrieve(out iByIndex, out lruNode, iPage))
                {
                    myCache.Hit(lruNode);
                }
                else
                {   // cache miss, loading from stream
                    iByIndex = myCache.Miss(iByIndex, iPage);
                    EasyLRUCache.CachePageBase cpb = myCache[iByIndex];
                    if (cpb.Buf == null || cpb.Buf.Length != myPageSize)
                    {
                        cpb.Buf = new byte[myPageSize];
                    }
                    myStream.Seek(iPage * myPageSize, SeekOrigin.Begin);
                    cpb.nLen = myStream.Read(cpb.Buf, 0, myPageSize);
                }

                if (myCache[iByIndex].nLen < nSizeInPage)
                {
                    bExhausted = true;
                    nSizeInPage = myCache[iByIndex].nLen;
                }

                MemCopy(buf, nBufOffset, myCache[iByIndex].Buf, iBeginInPage, nSizeInPage);

                nBufOffset += nSizeInPage;
                nOffset += nSizeInPage;
                nSize -= nSizeInPage;
            }

            return (nTotalRead - nSize);
        }

    /* private data members */

        protected EasyLRUCache myCache;
        protected Stream myStream;   // underlying stream
        protected int myPageSize;
    }
}
